package com.luych.toolbox.base.service.service;

import com.luych.toolbox.base.service.constant.Constant;
import com.luych.toolbox.base.service.dao.TaskDao;
import com.luych.toolbox.base.service.dao.TaskLogDao;
import com.luych.toolbox.base.service.entity.Task;
import com.luych.toolbox.base.service.entity.TaskLog;
import com.luych.toolbox.common.enums.TaskStatus;
import com.luych.toolbox.common.enums.TaskType;
import com.luych.toolbox.common.event.base.service.publisher.WebSocketTaskPublisher;
import com.luych.toolbox.common.feign.intf.base.service.UserFeign;
import com.luych.toolbox.common.feign.param.base.service.TaskParam;
import com.luych.toolbox.common.feign.param.gateway.center.WebSocketTask;
import com.luych.toolbox.common.feign.view.base.service.TaskLogView;
import com.luych.toolbox.common.feign.view.base.service.TaskView;
import com.luych.toolbox.common.page.PageParam;
import com.luych.toolbox.common.page.PageResult;
import com.luych.toolbox.common.service.base.BaseConvert;
import com.luych.toolbox.common.service.base.BaseService;
import com.luych.toolbox.common.service.exception.HttpException;
import com.luych.toolbox.common.service.page.SpringPageParamAdapter;
import com.luych.toolbox.common.service.page.SpringPageResultAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class TaskService extends BaseService<TaskView, TaskParam, Task> {

    private static final BaseConvert<TaskParam, Task> taskParamToEntity = Task::new;
    private static final BaseConvert<Task, TaskView> taskEntityToView = TaskView::new;
    private static final BaseConvert<TaskLog, TaskLogView> taskLogEntityToView = TaskLogView::new;

    private final TaskDao taskDao;
    private final TaskLogDao taskLogDao;
    private final UserFeign userFeign;
    private final WebSocketTaskPublisher taskPublisher;

    @Autowired
    public TaskService(TaskDao taskDao, TaskLogDao taskLogDao, UserFeign userFeign, WebSocketTaskPublisher taskPublisher) {
        super(taskDao, taskParamToEntity, taskEntityToView);
        this.taskDao = taskDao;
        this.taskLogDao = taskLogDao;
        this.userFeign = userFeign;
        this.taskPublisher = taskPublisher;
    }

    @Override
    protected TaskView fill(TaskView view) {
        taskDao.findById(view.getId()).map(Task::getOwnerId).map(userFeign::get).ifPresent(view::setOwner);
        view.setLogs(taskLogDao.findAllByTaskId(view.getId()).stream().map(taskLogEntityToView).collect(Collectors.toList()));
        return view;
    }

    public PageResult<TaskView> pageByKeyword(String keyword, TaskStatus status, PageParam pageParam) {
        Specification<Task> specification = (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (status != null) {
                Predicate statusEqual = criteriaBuilder.equal(root.get("status"), status);
                predicates.add(statusEqual);
            }
            if (!StringUtils.isEmpty(keyword)) {
                Predicate nameLike = criteriaBuilder.like(root.get("name"), '%' + keyword + '%');
                Predicate descriptionLike = criteriaBuilder.like(root.get("description"), '%' + keyword + '%');
                Predicate keywordLike = criteriaBuilder.or(nameLike, descriptionLike);
                predicates.add(keywordLike);
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
        Map<TaskType, Long> evaluateMap = new HashMap<>();
        for (TaskType taskType : TaskType.values()) {
            Long avgCost = taskDao.avgCostByType(taskType);
            evaluateMap.put(taskType, avgCost == null ? 0 : avgCost);
        }

        Pageable pageable = new SpringPageParamAdapter(pageParam);
        Page<Task> page = taskDao.findAll(specification, pageable);
        PageResult<Task> pageResult = new SpringPageResultAdapter<>(page);
        return pageResult.convert(taskEntityToView).convert(this::fill).convert(taskView -> {
            taskView.setEvaluate(evaluateMap.get(taskView.getType()));
            return taskView;
        });
    }

    public void start(String id) {
        Task task = taskDao.findById(id).orElseThrow(() ->
                new HttpException(HttpStatus.NOT_FOUND, Constant.TASK_NOT_FOUND));
        task.setStatus(TaskStatus.RUNNING);
        task.setStartDate(new Date());
        task.setStartLong(System.currentTimeMillis());
        taskDao.save(task);

        taskPublisher.send(new WebSocketTask(WebSocketTask.Type.START, task.getId()));
    }

    public void complete(String id, TaskStatus status, String stdout) {
        Task task = taskDao.findById(id).orElseThrow(() ->
                new HttpException(HttpStatus.NOT_FOUND, Constant.TASK_NOT_FOUND));
        task.setStatus(status);
        task.setStdout(stdout);
        task.setFinishDate(new Date());
        task.setFinishLong(System.currentTimeMillis());
        task.setCost(task.getFinishLong() - task.getStartLong());
        taskDao.save(task);

        taskPublisher.send(new WebSocketTask(WebSocketTask.Type.COMPLETE, task.getId()));
    }
}
